#pragma once

#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"

using namespace MutexModule;
using namespace SemModule;

static const int gcap = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int cap = gcap)
        :_cap(cap)
        ,_rq(cap)
        ,_blank_sem(cap)
        ,_p_step(0)
        ,_data_sem(0)
        ,_c_step(0)
    {}

    // 生产者
    void Enqueue(const T& in)
    {
        // 1. 空位置信号量大于0，信号量减1并返回;
        // 空位置信号量等于0，则阻塞直到信号量大于0被唤醒
        _blank_sem.P();
        _pmutex.Lock();

        // 2. 生产数据
        _rq[_p_step] = in;
        // 3. 更新下标
        ++_p_step;
        // 4. 维护环形特性
        _p_step %= _cap;
        // 5. 数据信号量加1
        // 如果队列为空，数据信号量则为0，在阻塞等待，此时会唤醒数据信号量
        _pmutex.Unlock(); 
        _data_sem.V();
    }

    // 消费者
    void Pop(T* out)
    {
        // 1. 获取信号量
        _data_sem.P();
        _cmutex.Lock();
        // 2. 消费数据
        *out = _rq[_c_step];
        // 3. 更新下标
        ++_c_step;
        // 4. 维护环形特性
        _c_step %= _cap;
        // 5. 释放信号量
        _cmutex.Unlock();
        _blank_sem.V();
    }

    ~RingQueue() {}
private:
    std::vector<T> _rq;
    int _cap;

    // 生产者
    Sem _blank_sem; // 空位置
    int _p_step; // 下一个空位置的下标

    // 消费者
    Sem _data_sem; // 数据
    int _c_step; // 下一个数据的下标

    // 维护多生产，多消费
    Mutex _pmutex;
    Mutex _cmutex;
};
